#! /usr/bin/env python3

"""
媒体文件模型
"""

from psutil import cpu_count
from PySide2.QtCore import Signal, QObject, QThread
from collections import namedtuple
from proglog import ProgressBarLogger
from moviepy.editor import concatenate_videoclips, VideoFileClip, VideoClip

clip_slice = namedtuple('Clip', ['name', "pose_s", 'pose_e', 'speed'])


class CClipProgBar(ProgressBarLogger):
    def __init__(self, init_state=None, bars=None, leave_bars=False,
                 ignored_bars=None, logged_bars='all', notebook='default',
                 print_messages=True, min_time_interval=0,
                 ignore_bars_under=0):
        super(CClipProgBar, self).__init__(init_state=init_state, bars=bars,
                                           ignored_bars=ignored_bars,
                                           logged_bars=logged_bars,
                                           ignore_bars_under=ignore_bars_under,
                                           min_time_interval=min_time_interval)
        self._total = 0
        self._cb_bar = None
        self._cb_message = None

    def set_bar_callback(self, cb):
        self._cb_bar = cb

    def set_message_callback(self, cb):
        self._cb_message = cb

    def bars_callback(self, bar, attr, value, old_value=None):
        # print("attr: ", attr, ' value: ', value)
        if attr == 'total':
            self._total = value
        elif attr == "index":
            if self._cb_bar is not None:
                self._cb_bar(value * 100 // self._total)

    def callback(self, **kw):
        if 'message' in kw and kw['message']:
            print('callback: ', kw['message'])
            if self._cb_message is not None:
                msg: str = kw['message'].rstrip()
                msg = msg.lower()
                msg = msg.removeprefix('moviepy - ')
                self._cb_message(msg)


class CClipWorker(QThread):
    EventMessage = Signal(str)
    EventBar = Signal(int)

    def __init__(self, parent=None, bar_cb=None, message_cb=None):
        super(CClipWorker, self).__init__(parent)
        self.out_file = ''
        self.mute = False
        self.video_set = None
        self.audio_set = None
        self.bar = CClipProgBar()
        self.bar.set_bar_callback(bar_cb)
        self.bar.set_message_callback(message_cb)

    def to_clip(self, out_file, mute, videos, audios):
        self.out_file = out_file
        self.mute = mute
        self.video_set = videos
        self.audio_set = audios
        self.start()

    @staticmethod
    def parse_pose(pose: str) -> int:
        pose_list = pose.split(":")
        if len(pose_list) != 3:
            raise ValueError('invalid pose ' + pose)
        return int(pose_list[0]) * 3600 + int(pose_list[1]) * 60 + int(pose_list[2])

    def run(self):
        vfc_map = dict()  # file_name: VideoFileClip()
        video_clips = list()
        for item in self.video_set:
            line: clip_slice = item
            file_name = line.name
            if file_name not in vfc_map:
                vfc = VideoFileClip(file_name)
                if self.mute:
                    vfc = vfc.without_audio()
                vfc_map[file_name] = vfc
            vfc: VideoFileClip = vfc_map[file_name]
            pose_s: int = self.parse_pose(line.pose_s)
            pose_e: int = self.parse_pose(line.pose_e)
            print("CClipWorker ", file_name, " pose: ", pose_s, " duration:", pose_e)
            video_clips.append(vfc.subclip(pose_s, pose_e))
        n_cpu = cpu_count()
        print('cpu_count: ', n_cpu)
        final_clis = concatenate_videoclips(video_clips)
        final_clis.write_videofile(self.out_file, threads=n_cpu - 1, codec='h264_nvenc', logger=self.bar)
        for key in vfc_map:
            vfc: VideoFileClip = vfc_map[file_name]
            vfc.close()


class CClipFile(QObject):
    VideoFileChanged = Signal(set)
    AudioFileChanged = Signal(set)
    EventMessage = Signal(str)
    EventBar = Signal(int)

    def __init__(self, parent=None):
        super(CClipFile, self).__init__(parent)
        self.worker = CClipWorker(self, lambda p: self.EventBar.emit(p), lambda p: self.EventMessage.emit(p))
        self.video_files: set = set()
        self.audio_files: set = set()

    def add_video_file(self, file_path: str):
        if file_path in self.video_files:
            raise FileExistsError
        self.video_files.add(file_path)
        self.VideoFileChanged.emit(self.video_files)

    def add_audio_file(self, file_path: str):
        if file_path in self.audio_files:
            raise FileExistsError
        self.audio_files.add(file_path)
        self.AudioFileChanged.emit(self.audio_files)

    # videos 中的元素是 clip_slice
    # audios 中的元素是 clip_slice, 可以为None, 为None为原声
    def clip_and_save(self, out_file: str, videos: list, audios: list, mute: bool):
        print('clip_and_save: ', out_file)
        print("videos: ", videos)
        print("audios: ", audios)
        if self.worker.isRunning():
            raise RuntimeError("has started")
        self.worker.to_clip(out_file, mute, videos, audios)


clipfile = CClipFile()
